package com.wzh.core.provider;

import com.wzh.core.api.entity.RpcRequest;
import com.wzh.core.api.entity.RpcResponse;
import com.wzh.core.annotation.RpcProvider;
import com.wzh.core.utils.RpcMethodUtils;
import com.wzh.core.utils.TypeUtils;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * @description: 实现ApplicationContextAware接口，可在Bean启动时获取所有服务提供者
 * @author: wangzouhuax
 */
@Slf4j
public class ProviderBootstrap implements ApplicationContextAware {

    private static final MultiValueMap<String, ProviderMetaData> serviceMap = new LinkedMultiValueMap<>();

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


    /**
     * 在对象在创建完，还没有初始化的时候获取所有服务提供者
     */
    @PostConstruct
    public void buildProviderServiceMap() {
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(RpcProvider.class);
        for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) {
            Class<?>[] interfaces = entry.getValue().getClass().getInterfaces();
            for (Class<?> anInterface : interfaces) {
                Method[] methods = anInterface.getMethods();
                for (Method method : methods) {
                    if (!RpcMethodUtils.checkRpcMethod(method)) {
                        ProviderMetaData providerMetaData = new ProviderMetaData();
                        providerMetaData.setMethod(method);
                        providerMetaData.setServiceNameImpl(entry.getValue());
                        providerMetaData.setMethodSignature(RpcMethodUtils.getMethodSignature(method));
                        serviceMap.add(anInterface.getCanonicalName(), providerMetaData);
                    }
                }
            }
        }

    }


    /**
     * rpc请求处理
     *
     * @param rpcRequest 请求参数 - rpcRequest 获取到consumer 端发送的参数是long类型->默认转化为Integer类型
     * @return rpc响应
     */
    public RpcResponse<Object> invokeMethod(RpcRequest rpcRequest) {
        RpcResponse<Object> rpcResponse = new RpcResponse<>();
        ProviderMetaData providerMetaData = fetchProviderMetaData(rpcRequest);
        if (providerMetaData == null) {
            rpcResponse.setErrorCode("service not found");
            rpcResponse.setStatus(false);
            rpcResponse.setData(null);
            return rpcResponse;
        }
        try {
            // 获取对应服务调用者方法
            Method method = providerMetaData.getMethod();
            // 参数如果是对象，则需要转换成对应类型，否则会报参数类型不匹配
            // 如果是对象，则rpcReqyest.args = LinkedHashMap<String, Object>
            Object[] args = TypeUtils.processArgs(method, rpcRequest.getArgs(), method.getGenericParameterTypes());
            Object result = method.invoke(providerMetaData.getServiceNameImpl(),args);
            rpcResponse.setStatus(true);
            rpcResponse.setData(result);
            return rpcResponse;
        } catch (Exception e) {
            log.error("invoke method error:{}", e.getMessage());
            rpcResponse.setErrorCode(e.getMessage());
            rpcResponse.setStatus(false);
            rpcResponse.setData(null);
            return rpcResponse;
        }
    }

    private ProviderMetaData fetchProviderMetaData(RpcRequest rpcRequest) {
        return serviceMap.get(rpcRequest.getServiceName()).stream()
                 .filter(metaData -> metaData.getMethodSignature().equals(rpcRequest.getMethodSignature()))
                .findAny()
                .orElse(null);
    }


    private Method findMethod(Object providerObj, String methodName) {
        if (providerObj == null) {
            throw new RuntimeException("providerObj is null");
        }
        Method[] methods = providerObj.getClass().getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }
        return null;
    }


}
